前言
在上一篇文章 Android NDK 入门 - 初探 CMake 中,学习了如何在 AS 中使用 CMake 来开发 NDK,而编写 CMakeLists.txt 构建脚本是其中一个重要的环节,今天我们就来一起学习 CMakeLists.txt 的一些应用,介绍它在下面四种场景的用法:
- CMakeLists.txt 文件解析
- 使用 Android NDK 的 API
- 编写 so 库
- 使用 so 库
CMakeLists.txt 文件解析
把常用的语法说明下,关于 CMake
的语法,可以查看 官方的 API 说明
cmake_minimum_required(VERSION3.4.1)
指定 CMake 最低版本add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
用于向 CMake 添加依赖源文件或库,指令需传入三个参数(函数库名称、库类型、依赖源文件相对路径)函数库名称:生成函数库的名称,决定了最终生成的共享库的名字,例如我们将共享库的名字定义为 native-lib ,那么最终生成的 so 文件将在前面加上 lib 前缀即 libnative-lib.so 或 libnative-lib.a ,但是我们在代码中加载该共享库的时候,仍然应当使用 native-lib ,也就是像下面这样
static {
System.loadLibrary(“native-lib”);
}库类型:动态库为 SHARED ,静态库为 STATIC,我们可以指定根据源文件编译出来的是静态库还是共享库,这里简单提一下两者的区别:
- 静态库:以.a结尾。静态库在程序链接的时候使用,链接器会将程序中使用到函数的代码从库文件中拷贝到应用程序中。一旦链接完成,在执行程序的时候就不需要静态库了。
- 共享库:以.so结尾。在程序的链接时候并不像静态库那样在拷贝使用函数的代码,而只是作些标记。然后在程序开始启动运行的时候,动态地加载所需模块。
依赖源文件相对路径,依赖的 c/cpp 文件(相对路径),如果我们有多个源文件,那么就在后面添加文件的路径即可。
find_library(log-lib log)
用于定位 NDK 中的库 ,需传入两个参数(path变量、ndk 库名称) ,具体有哪些 API 查看官网
- path变量:设置 path 变量的名称,这里为 NDK 中的日志库,文件位于
$NDK/platforms/android-<level>/<abi>/usr/lib
- ndk 库名称:指定 cmake 查询库的名称,即在 ndk 开发包中查询 liblog.so 函数库,将其路径赋值给 log-lib
- path变量:设置 path 变量的名称,这里为 NDK 中的日志库,文件位于
target_link_libraries(native-lib source1 source2 ... sourceN)
要将预构建库关联到您自己的原生库,需传入至少两个参数(指定目标库、链接的库)
- 指定目标库:与上面 add_library 指定的函数库名一致
- 链接的库:可链接 add_library ,find_library 中的库,其中 find_library 的库要填写变量 ${log-lib}
include_directories(src/main/cpp/include/)
为了确保 CMake 可以在编译时定位您的标头文件,填写头文件路径
使用 Android NDK 的 API
在 Android 系统当中,预制了一些标准的 NDK 库,这些库函数的目的就是让开发者能够在原生方法中实现之前在 Java 层开发的一些功能,我们可以通过 NDK 库 查找所需要的 API 。
因为这些库已经预制在系统当中了,所以如果我们要调用这些库中的函数,那么不需要将其打包到 APK 当中,所需要做的就是向 CMake 提供希望使用的库名称,并将其关联到自己的原生库,最后在原生代码中引入相应的头文件,调用方法就可以了。
下面,我们再来创建一个新项目(Including C++ support),研究 NDK 的 API。
创建项目后,打开 CMakeLists.txt ,看到引用了 NDK 的 log 库,并链接到 native-lib
既然引用了 log 库,那我们就在 native-lib 里调用 log 库
在 MainActivity 里定义 native 接口,按 ALT+ENTER 自动生成 C 函数
public native void printByJNI(String tag, String content); |
编写 printByJNI 函数
最后在 Activity 里调用 printByJNI(TAG, "onCreate()");
,查看结果。本来打算再引用个 NDK 的 API,但代码很复杂 ,倒不如自己写个简单的 so 库,再玩玩 CMakeLists 的配置 。
编写 so 库
观察上面的 log 库,发现引入一个 so 库需要 libxxx.so 文件和 .h 文件。OK,编写一个简单的计算器 so 库。
在 cpp 目录下创建 calc-lib ,注意要同时生成 .cpp 和 .h 文件
在 cpp 目录下创建 include 文件夹,把 .h 文件放进去
编写 calc-lib.h 头文件,定义方法
|
编写 calc-lib.cpp 文件,实现方法
|
编写完 calc-lib 库后,要修改 CMakeLists ,然后我们再通过 JNI 测试下我们的库是否能用
1. add_library 增加一个库,这个上面已经说过 |
每次修改 CMakeLists 都需要执行 Refresh Linked C++ Project
和 Clean Project
,等待 gradle 刷新完
编写 Java 接口,然后生成 JNI 函数,再调用我们的 calc-lib 库,基本上就这样。
最后启动 App,查看结果。
使用 so 库
经过上面的折腾,我们实现了在同个 App 内调用我们编写的 so 库,那怎么让别人使用我们的 so 库,同时不公开代码呢(主要实现代码在 calc-lib.cpp 内)
继续折腾,把 so 库提取出来,导入到项目 ,然后删除 calc-lib.cpp,最后通过 native-lib 调用。
Build APK 后,在 app/build/intermediates/cmake
下会生成 so 库,我们把 libcalc-lib.so 拷贝出来
在 app/src/main/
下创建 jniLibs 文件夹,把 so 库复制进去
删除 app/src/main/cpp/calc-lib.cpp
修改 CMakeLists
1. add_library 通过 IMPORTANT 标志告知 CMake 只希望将库导入到项目中 |
执行 Refresh Linked C++ Project
和 Clean Project
,等待 gradle 刷新完,最后启动 App,查看结果。
小结
这篇文章简单的解释 CMakeLists 文件的配置,如何导入 Android NDK 的 API,如何链接 so 库到 JNI,如何编写和使用 so 库,接下来会继续学习 ndk-build 的方式开发 Android NDK,对比两种方式。
参考: